﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FinPlusCommon;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class FinPlusAccumulator: FinPlusComponent 
    {
        public override string Id { get; set; }
        public override string[] States { get { return new string[] { }; } }
        public override string[] TestStates { get { return new string[] { "ModelTest", "IsValid" }; } }
        public override IFinPlusFunctions Model { get { return _model; } }
        public override string ModelTest { get { return Model.ToString(); } }
        public override bool IsValid { get; set; }

        private enum Format { Base, ColDetail, RowDetail, RowTotal, ColTotal, Unknown }

        private Accumulator _base;
        private readonly IDictionary<string, Accumulator> _accum;
        private readonly List<Format> _include;
        private readonly IFinPlusFunctions _model;
        private readonly string[] _keyLabels;
        private readonly string _name;
        private readonly int _rounding;

        //construct
        public FinPlusAccumulator(string name, string keyLabels, string include, string rounding) 
        {
            _name = name;
            _keyLabels = u.Split(keyLabels,',');
            _include = u.Split(include,',').Select(f=> u.EnumTryParse<Format>(f, Format.Unknown)).ToList();
            _rounding = int.Parse(rounding);
            _accum = new Dictionary<string, Accumulator>();
            _model = new Functions();
            IsValid = true;
        }

        //common control interface
        public override void Dock(object component, string dock)
        {
            IsValid = false;
            throw new NotImplementedException();
        }

        public override void Initialise()
        {

        }

        public override void CommandRouter(IFinPlusFunction func) { }
        
        public override void Dispose() { }

        //public
        public void Refresh()
        {
            _model.Refresh();
        }

        public bool TryAdd(string key, object[,] value, int row, bool isBase = false, char keyDelimitor = '|')
        {
            try
            {
                var isColVisible = _include.Contains(Format.ColDetail) && isBase ? _include.Contains(Format.Base) : true;
                var accum = GetAccum(key, keyDelimitor, isColVisible);

                var totalChange = 0.0;
                double val = 0.0;

                if (isBase)
                    _base = accum;

                if (double.TryParse(value[row, value.GetUpperBound(1)].ToString(), out val))
                    totalChange += AddValue(accum, value[row, 0].ToString(), val, _rounding, keyDelimitor, !isBase);
                else
                    return false;

                if (_include.Contains(Format.RowTotal)) 
                    AddValue(accum, _name, totalChange, _rounding, keyDelimitor, false, true);

                return true;
            }
            catch
            {
                return false;
            }
        }

        //private
        private double AddValue(Accumulator accum, string key, double value, int rounding, char keyDelimitor, bool baseDiff, bool isTotal = false)
        {
            if(baseDiff && _base != null) 
                value -= (double)_base.Values.Value(key, 0.0);

            var change = accum.Update(key, value, rounding, isTotal, isTotal, isTotal || _include.Contains(Format.RowDetail));

            return GetAccum(accum.TotalKey, keyDelimitor, true).Update(key, change, rounding, true, isTotal, _include.Contains(Format.ColTotal));
        }

        private Accumulator GetAccum(string key, char keyDelimitor, bool isVisible)
        {
            Accumulator accum;
            if (!_accum.ContainsKey(key))
            {
                var totalKey = key.Substring(0, key.LastIndexOf("|") + 1) + "Total";
                if (_include.Contains(Format.ColTotal) && !_accum.ContainsKey(totalKey))
                    _model.Add((_accum[totalKey] = new Accumulator(_name, _keyLabels, totalKey, keyDelimitor, User, true)).Model);

                _accum[key] = accum = new Accumulator(_name, _keyLabels, key, keyDelimitor, User, _include.Contains(Format.ColDetail));
                
                if (isVisible) _model.Add(accum.Model);
            }
            else
            {
                accum = _accum[key];
            }

            return accum;
        }

        //private classes
        private class Accumulator 
        {
            public IFinPlusFunction Model { get; private set; }
            public IFinPlusFunction Values { get; private set; }
            public string TotalKey { get; private set; }
            public double Total { get; private set; }

            private static int _id;

            public Accumulator(string name, string[] keyLabels, string key, char keyDelimitor, string user, bool isVisible = true)
            {
                _id++;
                var keys = key.Split(keyDelimitor);
                TotalKey = key.Substring(0, key.LastIndexOf("|") + 1) + "Total";
                Values = new Function(name, _id.ToString(), user, _id);
                Model = new Function(name, _id.ToString(), user, _id);
                Model.TryAdd(Values.Add(name, 0.0));

                if(isVisible) 
                    for (int x = 0; x < keyLabels.Length; x++)
                        Model.TryAdd(Values.Add(keyLabels[x], keys[x]));
            }

            public double Update(string key, double value, int rounding, bool IsChange, bool isTotal, bool isVisible = true)
            {
                value = Math.Round(value, rounding);
                var item = Values.Get(key);
                var pValue = 0.0;

                if (item != null)
                {
                    pValue = (double)Values.Value(key);
                    item.Set(value += IsChange ? pValue :  0, null, false);
                }
                else
                {
                    item = Values.Add(key, 0.0);
                    if (isVisible) 
                        Model.TryAdd(item);
                    item.Set(value, null, false);
                }

                if (!isTotal)
                    Total += value;

                return value -= pValue;
            }
        }
    }
}
